#! /usr/bin/env python
"""
::BOH
$Id: alert.py,v 1.38 2006/05/02 03:00:00

Copyright (c) 2006 George Bobeck. All rights reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
::EOH

Alert daemon
"""
import sys, os
from time import *
from datetime import *
import MySQLdb
import smtplib
from email.MIMEText import MIMEText

"""
Error Codes:
    0000        Alert Daemon Activated
    0404        Sensor Gone Inactive
    0666        Sensor Temperature Output Bad Data
    0H20        Sensor humidity output above 100, Bad Data
    1000        Temperature Related
    1001        Near Min Temperature
    1011        Near Max Temperature
    1002        At Min Temperture
    1012        At Max Temperture
    1003        Exceeded Min Temperature
    1013        Exceeded Max Temperature
    2001        Near Min Humidity
    2011        Near Max Humidity
    2002        At Min Humidity
    2012        At Max Humidity
    2003        Exceeded Min Humidity
    2013        Exceeded Max Humidity
    2H20        Humidity at 100%
Error Levels:
    X    Alert Daemon Activated
    0    Notice
    1    Low
    2    Medium
    3    Holy Crap, Batman!
For Temperature:
    Level 0:
        0666: temp < -30 C or temp > 85 C
    Level 1:
       min + 3 -> min + 1.5, max - 3 -> max - 1.5 
    Level 2:
        min -> min + 1.5 , max - 1.5 -> max
    Level 3:
        temp < min, temp > max
For humidity:
    X <=0 and x > 100 are ignored
    level 1:
        min + 3 - > min + 5, max -5 -> max - 3
    level 2:
        min -> min + 2, max -2 -> max
    level 3:
        below min, above max
"""

def conf(location):
    f = open(location,'r')
    a = f.readlines()
    fl = []
    var_list = []
    for e in a:
        if "#" not in e:
            e = e.strip('\n')
            e = e.strip('\r')
            if e == '':
                pass
            else:
                e = e.split("\t")
                try:
                    e.remove('')
                except:
                    pass
            var_list.extend(e)
    return var_list
global smtpserver
global resolution
global toaddr
global frmaddr
global verbose

#global smtp_verify
#smtp_verify=False

confs = conf(sys.argv[ 1 ])
verbose = bool(confs[confs.index("verbose")+1])
smtpserver = confs[confs.index("smtpserver")+1]
#
# change to conf file 
#
""" THIS IS LEFT FOR A FUTURE VERSION
smtp_verify = confs[confs.index("smtp_verify")+1]
if smtp_verify:
    global smtp_id
    global smtp_passwd
    smtp_id = confs[confs.index("smtp_id")+1]
    smtp_passwd = confs[confs.index("smtp_passwd")+1]
"""

toaddr = confs[confs.index("toaddr")+1]
frmaddr = confs[confs.index("frmaddr")+1]
resolution = int(confs[confs.index("resolution")+1])
dbhost = confs[confs.index("dbhost")+1]
dbport = int(confs[confs.index("dbport")+1])
dbuser = confs[confs.index("dbuser")+1]
dbpasswd = confs[confs.index("dbpasswd")+1]
dbase = confs[confs.index("dbase")+1]

if verbose:
    print "Alert Daemon Settings"
    print "Mail Server: ",smtpserver
    print "Resolution (in senconds): ",resolution
    print "Database Settings"
    print "Host: ",dbhost
    print "Port: ",dbport
    print "User: ",dbuser
    print "(Password Not Printed)"
    print "Database: ",dbase

def dbconnect():
    global ms
    ms = MySQLdb.connect(host = dbhost,port = dbport ,user = dbuser,passwd=dbpasswd,db=dbase)
    global cur
    cur = ms.cursor()

def Temp_gen_alert(temp,t_min = 5, t_max = 35):
    """
    Default temp. values are: 
      t_min = 5 (41 F) 
      t_max = 35 (95 F)
    These are based off of the environmental 
    params of an IBM ThinkPad T43 laptop.
    
    This function returns a tuple.
    """
    alert = None
    level = None
    if (temp >= -30) and (temp <= 85):
        if (temp <= (t_min + 3)) and (temp > (t_min + 1.5)):
            level = "1"
            alert = "1001"
        elif (temp >= (t_max - 3)) and (temp < (t_max - 1.5)):
            level = "1"
            alert = "1011"
        elif (temp <= (t_min + 1.5)) and (temp >= t_min):
            level = "2"
            alert = "1002"
        elif (temp >= (t_max - 1.5)) and (temp <= t_max):
            level = "2"
            alert = "1012"
        elif (temp < t_min):
            level = "3"
            alert = "1003"
        elif (temp > t_max):
            level = "3"
            alert = "1013"
        else:
            level = None
            alert = None
    else:
        level = "0"
        alert = "0666"
    
    return level, alert

def Humid_gen_alert(humid, h_min = 10, h_max = 85):
    """
    Default humid. values are:
      h_min = 10
      h_max = 85
    These are based off of the environmental 
    params of a Cisco Catalyst WS-C3750G-24TS-E 10/100/1000 switch.
    
    This function returns a tuple.
    """
    alert = None 
    level = None
    
    if (humid > 0):
        if (humid > (h_min + 3)) and (humid <= (h_min + 5)):
            level = "1"
            alert = "2001"
        elif (humid <= (h_max - 3)) and (humid >= (h_max - 5)):
            level = "1"
            alert = "2011"
        elif (humid <= (h_min + 3)) and (humid >= h_min):
            level = "2"
            alert = "2002"
        elif (humid >= (h_max - 3)) and (humid <= h_max):
            level = "2"
            alert = "2012"
        elif (humid < h_min):
            level = "3"
            alert = "2003"
        elif (humid > h_max) and (humid < 100):
            level = "3"
            alert = "2013"
        elif (humid == 100):
            level = "3"
            alert = "2H20"
        elif (humid > 100):
            level = "0"
            alert = "0H20"
        else:
            # If Humid > 0 and not alertable
            level = None
            alert = None
    else:
        # Humid <= 0
        level = None
        alert = None
    
    return level, alert

def V1_gen_alert(v1, v1_min, v1_max):
    alert = None 
    level = None
    
    if v1_min == v1_max:
        level = None
        alert = None
    elif v1 < v1_min:
        level = "3"
        alert = "3003"
    elif v1 > v1_max:
        level = "3"
        alert = "3013"
    elif v1 == v1_min:
        level = "2"
        alert = "3002"
    elif v1 == v1_max:
        level = "2"
        alert = "3012"
    
    else:
        level = None
        alert = None
    return level, alert

def V2_gen_alert(v2, v2_min, v2_max):
    alert = None 
    level = None
    
    if v2_min == v2_max:
        level = None
        alert = None
    elif v2 < v2_min:
        level = "3"
        alert = "4003"
    elif v2 > v2_max:
        level = "3"
        alert = "4013"
    elif v2 == v2_min:
        level = "2"
        alert = "4002"
    elif v2 == v2_max:
        level = "2"
        alert = "4012"
    else:
        level = None
        alert = None
    return level, alert

def Alert_msg_gen(sid,location,alert):
    sl = "Sensor %s, located at %s," %(sid, location)
   
    if alert == "0404":
        alert_msg = sl + "has gone inactive."
    elif alert == "0666":
        alert_msg = sl + "has output a really invalid temperature value"
    elif alert == "0H20":
        alert_msg = sl + "has output a really invalid humidity value"
    elif alert == "1001":
        alert_msg = sl + "is nearing the minimum temperature limit."
    elif alert == "1011":
        alert_msg = sl + "is nearing the maximum temperature limit."
    elif alert == "1002":
        alert_msg = sl + "is at the minimum temperature."
    elif alert == "1012":
        alert_msg = sl + "is at the maximum temperature."
    elif alert == "1003":
        alert_msg = sl + "is below the minimum temperature!"
    elif alert == "1013":
        alert_msg = sl + "is above the maximum temperature!"
    elif alert == "2001":
        alert_msg = sl + "is nearing the minimum humidity limit."
    elif alert == "2011":
        alert_msg = sl + "is nearing the maximum humidity limit."
    elif alert == "2002":
        alert_msg = sl + "is at the minimum humidity limit."
    elif alert == "2012":
        alert_msg = sl + "is at the maximum humidity limit."
    elif alert == "2003":
        alert_msg = sl + "is below the minimum humidity limit!"
    elif alert == "2013":
        alert_msg = sl + "is above the maximum humidity limit!"
    elif alert == "3003":
        alert_msg = sl + "is below the minimum V1 limit!"
    elif alert == "3013":
        alert_msg = sl + "is above the maximum V1 limit!"
    elif alert == "4003":
        alert_msg = sl + "is below the minimum V2 limit!"
    elif alert == "4013":
        alert_msg = sl + "is above the maximum V2 limit!"
    else:
        alert_msg = sl + "is either at 100% humidity or under water!"

    return alert_msg

def st2dt(x):
    d = datetime(int(x[0:4]),int(x[5:7]),int(x[8:10]),int(x[11:13]),int(x[14:16]),int(x[17:19]))
    return d

def email_alert(toaddrs,fromaddr,sid,alert):  
    msg = MIMEText(alert)
    msg['Subject'] = "OWTF Alert: %s" %(sid)
    msg['From'] = fromaddr
    msg['To'] =toaddrs
    
    global smtpserver
    #global smtp_verify
    global verbose
    
    server = smtplib.SMTP(smtpserver)
    #if smtp_verify:
        #global smtp_id
        #global smtp_passwd
        #server.login(smtp_id, smtp_passwd)
    if verbose:
        server.set_debuglevel(1)
    server.sendmail(fromaddr, toaddrs, msg.as_string())
    server.quit()

def main():
    global resolution
    p2_time = None

    try:
        # try to connect to db
        dbconnect()
        global cur
        c_time = datetime.today()
        c_time = c_time.strftime("%Y-%m-%d %H:%M:%S")
        cur.execute("""insert ignore into alert values (" DAEMON STARTED ",%s,"X","0000")""",(c_time))
    except:
        print "Unable to Connect to Database"
        sys.exit(1)

    while 1:
        
        alert_queue = []
        c_time = datetime.today()
        if p2_time:
            p_time = p2_time
        else:
            #past time (p_time) is current time minus (resolution + 10)
            p_time = c_time - timedelta(seconds = (resolution + 10))
            p_time = p_time.strftime("%Y-%m-%d %H:%M:%S")
        del_time = c_time - timedelta(days = 32) #deleteion time
        inactive_time = c_time - timedelta(days = 7) #deleteion time
        c_time = c_time.strftime("%Y-%m-%d %H:%M:%S")
        del_time = del_time.strftime("%Y-%m-%d %H:%M:%S")
        inactive_time = inactive_time.strftime("%Y-%m-%d %H:%M:%S")

        try:
            global toaddr
            global frmaddr
            if verbose:
                print "Alert Process Running:"
            cur.execute("""delete from sensor_data where cron < %s""",(del_time))
            if verbose:
                print "Deleted Very Old Events.[DONE]"
            cur.execute("""select * from sensors where active = 'y' and cron <= %s """,(inactive_time))
            z = cur.fetchall()
            for i in z:                
                cur.execute("""insert ignore into alert values(%s,%s,%s,%s)""",(i[1],c_time,"0","0404"))
                alert_queue.append([i[1],Alert_msg_gen(i[1],i[2],"0404")])

            cur.execute("""update sensors set active = 'N' where cron < %s""",(inactive_time))
            
            if verbose:
                print "Set sensors which haven't spoken for 7 days to inactive.[DONE]"
            
            # start main alert process now!
            cur.execute("""select * from sensor_data where cron between %s and %s""",(p_time, c_time))
            x = cur.fetchall()
            cur.execute("""select sid,location from sensors""")
            sl_tup = cur.fetchall()            
            sl_dict = {}
            for sl in sl_tup:
                sl_dict[sl[0]]=sl[1]
            
            cur.execute("""select * from alertables""")
            custom_alertables = cur.fetchall()
            custom_alertables_dict = {}
            for ca in custom_alertables:
                custom_alertables_dict[ca[0]]=list(ca[1:9])
            if verbose:
                print "Running alert tests."
            for i in x:
                
                v1 = (None,None)
                v2 = (None,None)
                
                if i[0] in custom_alertables_dict:
                    
                    t = Temp_gen_alert(i[2], custom_alertables_dict[i[0]][0],custom_alertables_dict[i[0]][1])
                    h = Humid_gen_alert(i[3], custom_alertables_dict[i[0]][2],custom_alertables_dict[i[0]][3])
                    v1 = V1_gen_alert(i[2], custom_alertables_dict[i[0]][4],custom_alertables_dict[i[0]][5])
                    v2 = V2_gen_alert(i[3], custom_alertables_dict[i[0]][6],custom_alertables_dict[i[0]][7])
                    
                else:
                    t = Temp_gen_alert(i[2])
                    h = Humid_gen_alert(i[3])
                    
                if t != (None,None):
                    cur.execute("""insert ignore into alert values(%s,%s,%s,%s)""",(i[0],i[1],t[0],t[1]))
                    alert_queue.append([i[0],Alert_msg_gen(i[0],sl_dict[i[0]],t[1])])
                elif h != (None,None):
                    cur.execute("""insert ignore into alert values(%s,%s,%s,%s)""",(i[0],i[1],h[0],h[1]))
                    alert_queue.append([i[0],Alert_msg_gen(i[0],sl_dict[i[0]],h[1])])
                elif v1 != (None,None):
                    cur.execute("""insert ignore into alert values(%s,%s,%s,%s)""",(i[0],i[1],v1[0],v1[1]))
                    alert_queue.append([i[0],Alert_msg_gen(i[0],sl_dict[i[0]],v1[1])])
                elif v2 != (None,None):
                    cur.execute("""insert ignore into alert values(%s,%s,%s,%s)""",(i[0],i[1],v2[0],v2[1]))
                    alert_queue.append([i[0],Alert_msg_gen(i[0],sl_dict[i[0]],v2[1])])
                else:
                    pass

            try:
                for aq in alert_queue:
                    email_alert(toaddr,frmaddr,aq[0],aq[1])
            except:
                if verbose:
                    print "email function didn't work!"
                else:
                    pass
        
        except:
            if verbose:
                print "Something Failed!!!"
            else:
                pass
        p2_time = c_time
        if verbose:
            print "Done"
        #sleep (resolution)
        ms.close()
        sys.exit( 1 )

if __name__ == "__main__":
    try: 
        pid = os.fork() 
        if pid > 0:
            # exit first parent
            sys.exit(0) 
    except OSError, e: 
        print >>sys.stderr, "ERROR: fork #1 failed: %d (%s)" % (e.errno, e.strerror) 
        sys.exit(1)
    # decouple from parent environment
    os.chdir("/") 
    os.setsid() 
    os.umask(0) 
    # do second fork
    try: 
        pid = os.fork() 
        if pid > 0:
            # exit from second parent, print eventual PID before
            if verbose:
                print "Monitor PID %d" % pid 
            sys.exit(0) 
    except OSError, e: 
        print >>sys.stderr, "ERROR: fork #2 failed: %d (%s)" % (e.errno, e.strerror) 
        sys.exit(1) 
    main()

